/*
 * Copyright (c) laokz <laokz@foxmail.com>
 * VMWIFI is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <arpa/inet.h>
#include "vmwifi.h"
#include "vw.h"

/* store parameters used for sending response to guest */
struct TyGuestParam {
    int sk;                         /* socket */
    unsigned char mac[ETH_ALEN];    /* guest MAC */
};
static struct TyGuestParam g_ethPara = { .sk = -1 };

static int GetEthIfIndex(const char *iface)
{
    struct ifreq ifr = { 0 };
    int ret;

    strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1);
    ret = ioctl(g_ethPara.sk, SIOCGIFINDEX, &ifr);
    OUT_IF_ERR(ret);
    ret = ifr.ifr_ifindex;

OUT:
    return ret;
}

int VwWaitRequest(void)
{
    struct sockaddr_ll src;
    char buf[ETH_FRAME_LEN];
    int ret, len, retry;

    retry = 0;
    do {
        len = sizeof(struct sockaddr_ll);
        ret = recvfrom(g_ethPara.sk, buf, ETH_FRAME_LEN, 0, (void *)&src, (void *)&len);
        retry++;
    } while (ret < 0 && retry < 2);
    OUT_IF_ERR(ret);

    if (ret == 0) { /* this might not happen ? */
        ret = -1;
        LOG("guest terminated\n");
    } else if (ntohs(src.sll_protocol) != VW_ETHER_FRAME_TYPE) {
        ret = VW_CODE_INVALID;
        LOG("not VMWIFI protocol type: 0x%04x\n", ntohs(src.sll_protocol));
    } else {
        ret = *(short *)(buf + ETH_HLEN);   /* get `VwCodeT` code */
        if (ret <= VW_CODE_INVALID || ret >= VW_CODE_NUMS) {
            ret = VW_CODE_INVALID;
            LOG("unsupported request code: 0x%hx\n", ret);
        } else {
            LOG("received guest request code: 0x%hx\n", ret);
        }
    }

OUT:
    return ret;
}

/* data more than 1500 bytes will be truncated */
void VwSendResponse(const char srcMac[], const char *data, int len)
{
    unsigned char buf[ETH_FRAME_LEN];   /* not use dynamic allocation */
    struct ethhdr *p = (struct ethhdr *)buf;

    if (len > ETH_DATA_LEN) {
        len = ETH_DATA_LEN;
    }

    memcpy(p->h_dest, g_ethPara.mac, ETH_ALEN);
    if (srcMac) {   /* otherwise leave it to kernel */
        memcpy(p->h_source, srcMac, ETH_ALEN);
    }
    p->h_proto = htons(VW_ETHER_FRAME_TYPE);
    memcpy(buf + ETH_HLEN, data, len);

    len += ETH_HLEN;
    if (len < ETH_ZLEN) {
        memset(buf + len, 0, ETH_ZLEN - len);
        len = ETH_ZLEN;
    }

    (void)send(g_ethPara.sk, buf, len, 0);
    LOG("send response to guest %d bytes\n", len);
}

int InitVwEther(const char *iface)
{
    struct sockaddr_ll ethAddr = { 0 };
    int ret;

    g_ethPara.sk = socket(PF_PACKET, SOCK_RAW, htons(VW_ETHER_FRAME_TYPE));
    OUT_IF_ERR(g_ethPara.sk);

    if (iface == NULL) {
        iface = VW_BRIDGE_NAME_DFT;
    }
    ethAddr.sll_ifindex = GetEthIfIndex(iface);
    OUT_IF__1(ethAddr.sll_ifindex);

    ethAddr.sll_family = AF_PACKET;
    ret = bind(g_ethPara.sk, (struct sockaddr *)&ethAddr, sizeof(ethAddr));
    OUT_IF_ERR(ret);
    LOG("guest socket init success, bridge name:%s, index=%d\n", iface, ethAddr.sll_ifindex);

OUT:
    return ret;
}

void DeinitVwEther(void)
{
    if (g_ethPara.sk >= 0) {
        close(g_ethPara.sk);
    }
}
